using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Telerik.Web.Mvc;
using TelerikMvcGridCustomBindingHelper.Mapper;
using TelerikMvcGridCustomBindingHelper.Util;
namespace TelerikMvcGridCustomBindingHelper.DynamicQuery
{
    internal class DynamicWhereClause<TEntity, TViewModel> : BaseDynamicWhereClause<TEntity, TViewModel, string>
        where TEntity : class
        where TViewModel : class
    {
        private readonly List<object> _values = new List<object>();
        public object[] Values { get { return _values.ToArray(); } }

        private Expression<Func<TEntity, bool>> _customExpression;
        public Expression<Func<TEntity, bool>> CustomExpression { get { return RebindParameters(_customExpression); } }

        private static Expression<Func<TEntity, bool>> RebindParameters(Expression<Func<TEntity, bool>> predicate)
        {
            var expression = ParameterRebinder.ReplaceParameters(predicate.Parameters.ToDictionary(p => p), predicate.Body, true);
            return Expression.Lambda<Func<TEntity, bool>>(expression, predicate.Parameters[0]);
        }

        public DynamicWhereClause() { }

        public DynamicWhereClause(FilterDescriptor filterDescriptor, CaseInsensitive<TEntity> caseInsensitive, bool queryAcceptNullValuesWhenFiltering, ValueResolvers queryValueResolvers)
            : base(filterDescriptor, caseInsensitive, queryAcceptNullValuesWhenFiltering, queryValueResolvers)
        {
            if (Predicate.IsNotNullOrWhiteSpace())
            {
                _customExpression = (Expression<Func<TEntity, bool>>)
                    System.Linq.Dynamic.DynamicExpression.ParseLambda(typeof(TEntity), typeof(bool), Predicate, _values.ToArray());
            }
        }

        public override bool HasFilters()
        {
            return base.HasFilters() || CustomExpression != null;
        }

        public void AndAlso(DynamicWhereClause<TEntity, TViewModel> anotherDynamicWhereClause)
        {
            MergePredicateFrom(anotherDynamicWhereClause, false);
        }

        public void OrElse(DynamicWhereClause<TEntity, TViewModel> anotherDynamicWhereClause)
        {
            MergePredicateFrom(anotherDynamicWhereClause, true);
        }

        private void MergePredicateFrom(DynamicWhereClause<TEntity, TViewModel> anotherDynamicWhereClause, bool useOrElse)
        {
            _values.AddRange(anotherDynamicWhereClause._values);

            base.MergePredicateFrom(anotherDynamicWhereClause, useOrElse);

            if (Predicate == null)
            {
                _customExpression = anotherDynamicWhereClause.CustomExpression;
            }
            else if (CustomExpression == null)
            {
                _customExpression = (Expression<Func<TEntity, bool>>)
                    System.Linq.Dynamic.DynamicExpression.ParseLambda(typeof(TEntity), typeof(bool), Predicate, _values.ToArray());
            }
            else if (anotherDynamicWhereClause.CustomExpression != null)
            {
                _customExpression = useOrElse
                    ? CustomExpression.OrElse(anotherDynamicWhereClause.CustomExpression)
                    : CustomExpression.AndAlso(anotherDynamicWhereClause.CustomExpression);
            }
            else
            {
                var expression = (Expression<Func<TEntity, bool>>)
                    System.Linq.Dynamic.DynamicExpression.ParseLambda(typeof(TEntity), typeof(bool), Predicate, _values.ToArray());

                _customExpression = useOrElse
                    ? CustomExpression.OrElse(expression)
                    : CustomExpression.AndAlso(expression);
            }
        }

        protected override string OrElse(string left, string right)
        {
            return ConcatenatePredicates(left, right);
        }

        protected override string AndAlso(string left, string right)
        {
            return ConcatenatePredicates(left, right, false);
        }

        private static string ConcatenatePredicates(string left, string right, bool useOrElse = true)
        {
            if (left.IsNullOrWhiteSpace())
                return right;

            if (right.IsNullOrWhiteSpace())
                return left;

            var predicate = string.Format("{0} {1} {2}", left, useOrElse ? "||" : "&&", right);

            var tmpPredicate = predicate.Split('@');

            for (var i = 0; i < tmpPredicate.Length - 1; i++)
            {
                var position = 0;

                foreach (var _char in tmpPredicate[i + 1])
                {
                    int number;
                    if (int.TryParse(_char.ToString(GridModelMapper.ConfigurationObject.DefaultCulture), out number))
                        position++;
                    else
                        break;
                }

                predicate = string.Concat(i == 0 ? tmpPredicate[i] : predicate, "@", i, tmpPredicate[i + 1].Substring(position));
            }

            return predicate.Trim();
        }

        protected override string BuildPredicateThatAcceptNullValues(INullSubstitute nullSubstitutes, bool useNotEquals)
        {
            if (nullSubstitutes != null)
            {
                var caseInsensitive = false;

                IList<object> nullValues = new List<object>(nullSubstitutes.NullValues().Count);

                foreach (var nullValue in nullSubstitutes.NullValues())
                {
                    try
                    {
                        var changeType = Reflector.ChangeType(nullValue, GridPropertyInfo.PropertyType);
                        if (GridPropertyInfo.PropertyType == typeof(string) && changeType != null && nullSubstitutes.IsCaseInsensitive)
                        {
                            changeType = ((string)changeType).ToUpper(GridModelMapper.ConfigurationObject.DefaultCulture);
                            caseInsensitive = true;
                        }
                        nullValues.Add(changeType);
                    }
                    // ReSharper disable EmptyGeneralCatchClause
                    catch { }
                    // ReSharper restore EmptyGeneralCatchClause
                }

                var predicate = string.Empty;

                foreach (var nullValue in nullValues)
                {
                    if (nullValue != null)
                    {
                        _values.Add(nullValue);

                        predicate = BuildPredicateThatAcceptNullValues(predicate, useNotEquals, false, caseInsensitive);
                    }
                    else
                    {
                        predicate = BuildPredicateThatAcceptNullValues(predicate, useNotEquals);
                    }
                }

                return OrElse(Predicate, predicate);
            }

            return BuildPredicateThatAcceptNullValues(Predicate, useNotEquals);
        }

        private string BuildPredicateThatAcceptNullValues(string predicate, bool useNotEquals, bool compareToLiteralNull = true, bool caseInsensitive = false)
        {
            var property = caseInsensitive ? string.Format("{0}.ToUpper()", TargetProperty) : TargetProperty;
            var indexOrNull = compareToLiteralNull ? "null" : (caseInsensitive && ConvertedValue != null ? "@0.ToUpper()" : "@0");
            return OrElse(predicate, string.Format("{0} {1} {2}", property, useNotEquals ? "!=" : "==", indexOrNull));
        }

        protected override string BuildPredicateWithBooleanSubstitutes(IEnumerable<object> booleanSubstitutes)
        {
            Guard.If(ConvertedValue is bool == false)
                .Throw<ArgumentException>(string.Format("Property \"{0}.{1}\" was not of type boolean.", typeof(TEntity).Name, TargetProperty));

            switch (InnerFilterDescriptor.Operator)
            {
                case FilterOperator.IsEqualTo:
                    return OrElse(Predicate, GetBooleanSubstitutePredicate(booleanSubstitutes, "=="));

                case FilterOperator.IsNotEqualTo:
                    return OrElse(Predicate, GetBooleanSubstitutePredicate(booleanSubstitutes, "!="));

                default:
                    throw new NotSupportedException(string.Format("The method or expression \"{0}\" is not supported when the value is a boolean.", InnerFilterDescriptor.Operator));
            }
        }

        private string GetBooleanSubstitutePredicate(IEnumerable<object> substitutes, string expOperator)
        {
            var prop = TargetProperty;
            var predicate = string.Empty;
            var array = substitutes.Reverse().ToArray();

            for (var i = 0; i < array.Length; i++)
            {
                var substitute = array.ElementAt(i);
                var alias = string.Format("@{0}", predicate.Count(c => c == '@'));
                if ((GridPropertyInfo.PropertyType == typeof(bool?)) && substitute == null)
                {
                    _values.Add(false);
                    var exp = string.Format("{0}.HasValue {1} {2}", prop, expOperator, alias);
                    predicate = predicate.IsNullOrWhiteSpace() ? exp : string.Concat(predicate, " || ", exp);
                }
                else if (substitute == null)
                {
                    var exp = string.Format("{0} {1} null", prop, expOperator);
                    predicate = predicate.IsNullOrWhiteSpace() ? exp : string.Concat(predicate, " || ", exp);
                }
                else
                {
                    _values.Add(substitute);
                    var exp = string.Format("{0} {1} {2}", prop, expOperator, alias);
                    predicate = predicate.IsNullOrWhiteSpace() ? exp : string.Concat(predicate, " || ", exp);
                }
            }

            return predicate;
        }

        protected override string BuildPredicateCaseSensitive(object value)
        {
            if (GridPropertyInfo.CustomExpression != null)
            {
                BuildPredicateCaseSensitiveWithCustomExpression(GridPropertyInfo.CustomExpression, value);
                return null;
            }

            _values.Add(value);
            var property = TargetProperty;

            switch (InnerFilterDescriptor.Operator)
            {
                case FilterOperator.IsEqualTo:
                    return string.Concat(property, " == @0");

                case FilterOperator.IsNotEqualTo:
                    return string.Concat(property, " != @0");

                case FilterOperator.StartsWith:
                    return string.Concat(property, ".StartsWith(@0)");

                case FilterOperator.EndsWith:
                    return string.Concat(property, ".EndsWith(@0)");

                case FilterOperator.Contains:
                    return string.Concat(property, ".Contains(@0)");

                case FilterOperator.DoesNotContain:
                    return string.Concat("!", property, ".Contains(@0)");

                case FilterOperator.IsGreaterThan:
                    return string.Concat(property, " > @0");

                case FilterOperator.IsGreaterThanOrEqualTo:
                    return string.Concat(property, " >= @0");

                case FilterOperator.IsLessThan:
                    return string.Concat(property, " < @0");

                case FilterOperator.IsLessThanOrEqualTo:
                    return string.Concat(property, " <= @0");

                // I dont kown how to implement this
                //case FilterOperator.IsContainedIn:
                default:
                    throw new NotSupportedException(string.Format("The method or expression \"{0}\" is not supported.", InnerFilterDescriptor.Operator));
            }
        }

        private void BuildPredicateCaseSensitiveWithCustomExpression(LambdaExpression customExpression, object value)
        {
            Expression exp;

            switch (InnerFilterDescriptor.Operator)
            {
                case FilterOperator.IsEqualTo:
                    exp = Expression.Equal(customExpression.Body, Expression.Constant(value));
                    break;

                case FilterOperator.IsNotEqualTo:
                    exp = Expression.NotEqual(customExpression.Body, Expression.Constant(value));
                    break;

                case FilterOperator.StartsWith:
                    exp = Expression.Call(customExpression.Body, "StartsWith", null, Expression.Constant(value));
                    break;

                case FilterOperator.EndsWith:
                    exp = Expression.Call(customExpression.Body, "EndsWith", null, Expression.Constant(value));
                    break;

                case FilterOperator.Contains:
                    exp = Expression.Call(customExpression.Body, "Contains", null, Expression.Constant(value));
                    break;

                case FilterOperator.DoesNotContain:
                    exp = Expression.Not(Expression.Call(customExpression.Body, "Contains", null, Expression.Constant(value)));
                    break;

                case FilterOperator.IsGreaterThan:
                    exp = Expression.GreaterThan(customExpression.Body, Expression.Constant(value));
                    break;

                case FilterOperator.IsGreaterThanOrEqualTo:
                    exp = Expression.GreaterThanOrEqual(customExpression.Body, Expression.Constant(value));
                    break;

                case FilterOperator.IsLessThan:
                    exp = Expression.LessThan(customExpression.Body, Expression.Constant(value));
                    break;

                case FilterOperator.IsLessThanOrEqualTo:
                    exp = Expression.LessThanOrEqual(customExpression.Body, Expression.Constant(value));
                    break;

                // I dont kown how to implement this
                //case FilterOperator.IsContainedIn:
                default:
                    throw new NotSupportedException(string.Format("The method or expression \"{0}\" is not supported.", InnerFilterDescriptor.Operator));
            }

            _customExpression = Expression.Lambda<Func<TEntity, bool>>(exp, customExpression.Parameters);
        }

        protected override string BuildPredicateCaseInsensitive(string value)
        {
            _values.Add(value.ToUpper(GridModelMapper.ConfigurationObject.DefaultCulture));
            var property = string.Format("{0}.ToUpper()", TargetProperty);

            switch (InnerFilterDescriptor.Operator)
            {
                case FilterOperator.IsEqualTo:
                    return string.Concat(property, " == @0");

                case FilterOperator.IsNotEqualTo:
                    return string.Concat(property, " != @0");

                case FilterOperator.StartsWith:
                    return string.Concat(property, ".StartsWith(@0)");

                case FilterOperator.EndsWith:
                    return string.Concat(property, ".EndsWith(@0)");

                case FilterOperator.Contains:
                    return string.Concat(property, ".Contains(@0)");

                case FilterOperator.DoesNotContain:
                    return string.Concat("!", property, ".Contains(@0)");

                // I dont kown how to implement this
                //case FilterOperator.IsContainedIn:
                default:
                    throw new NotSupportedException(string.Format("The method or expression \"{0}\" is not supported.", InnerFilterDescriptor.Operator));
            }
        }
    }
}